/*
 * Copyright (C) 2009, Edmundo Albuquerque de Souza e Silva.
 *
 * This file may be distributed under the terms of the Q Public License
 * as defined by Trolltech AS of Norway and appearing in the file
 * LICENSE.QPL included in the packaging of this file.
 *
 * THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

package net.sf.fmj.media.codec.audio.ulaw;

import java.util.logging.Logger;

import javax.media.Buffer;
import javax.media.Format;
import javax.media.format.AudioFormat;

import net.sf.fmj.codegen.MediaCGUtils;
import net.sf.fmj.media.AbstractCodec;
import net.sf.fmj.utility.LoggerSingleton;

/**
 * 
 * 
 *
 */
public class Encoder extends AbstractCodec
{
	private static final Logger logger = LoggerSingleton.logger;

	public String getName()
	{
		return "ULAW Encoder";
	}
	
	public Encoder()
	{
		super();
		this.inputFormats = new Format[] {
				new AudioFormat(AudioFormat.LINEAR, -1.0, 16, 1, -1, AudioFormat.SIGNED, 16, -1.0, Format.byteArray),
				//new AudioFormat(AudioFormat.LINEAR, -1.0, 8, 1, -1, AudioFormat.SIGNED, 8, -1.0, Format.byteArray)	
				// using 8 bit input is kind of silly, since the whole point of ulaw is to encode 16 bits as 8 bits.
				// the quality will be bad, and we are better off using a rate converter to do all of the rate conversion at once.
				};
		
		// TODO: if force AudioFormat.LITTLE_ENDIAN, codecs leading up to this seem to freeze up or something.
		
	}
	
	// TODO: move to base class?
	protected Format[] outputFormats = new Format[] {new AudioFormat(AudioFormat.ULAW, -1.0, 8, 1, -1, AudioFormat.SIGNED, 8, -1.0, Format.byteArray)};
	
	public Format[] getSupportedOutputFormats(Format input)
	{
		if (input == null)
			return outputFormats;
		else
		{	
			if (!(input instanceof AudioFormat))
			{	logger.warning(this.getClass().getSimpleName() + ".getSupportedOutputFormats: input format does not match, returning format array of {null} for " + input); // this can cause an NPE in JMF if it ever happens.
				return new Format[] {null};
			}
			final AudioFormat inputCast = (AudioFormat) input;
			if (!inputCast.getEncoding().equals(AudioFormat.LINEAR) ||
				(inputCast.getSampleSizeInBits() != 16 && inputCast.getSampleSizeInBits() != Format.NOT_SPECIFIED) ||
				(inputCast.getChannels() != 1 && inputCast.getChannels() != Format.NOT_SPECIFIED) ||
				(inputCast.getSigned() != AudioFormat.SIGNED && inputCast.getSigned() != Format.NOT_SPECIFIED) ||
				(inputCast.getFrameSizeInBits() != 16 && inputCast.getFrameSizeInBits() != Format.NOT_SPECIFIED) ||
				(inputCast.getDataType() != null && inputCast.getDataType() != Format.byteArray)
				)
			{	
				logger.warning(this.getClass().getSimpleName() + ".getSupportedOutputFormats: input format does not match, returning format array of {null} for " + input); // this can cause an NPE in JMF if it ever happens.
				return new Format[] {null};
			}
			final AudioFormat result = new AudioFormat(AudioFormat.ULAW, inputCast.getSampleRate(), 8,
					1, -1, AudioFormat.SIGNED, 8, // endian-ness irrelevant for 8 bits
					inputCast.getFrameRate(), Format.byteArray);

			return new Format[] {result};
		}
	}

	public void open()
	{
	}
	
	public void close()
	{
	}
	
	private static final boolean TRACE = false;
	
	public int process(Buffer inputBuffer, Buffer outputBuffer)
	{
		if (TRACE) dump("input ", inputBuffer);
		
        if (!checkInputBuffer(inputBuffer))
        {
            return BUFFER_PROCESSED_FAILED;
        }

        if (isEOM(inputBuffer))
        {
            propagateEOM(outputBuffer);	// TODO: what about data? can there be any?
            return BUFFER_PROCESSED_OK;
        }
        
        final AudioFormat inputAudioFormat = (AudioFormat) inputBuffer.getFormat();
        
        byte[] outputBufferData = (byte []) outputBuffer.getData();
        final int requiredOutputBufferLength = inputBuffer.getLength() / 2;
        	
        if (outputBufferData == null || outputBufferData.length < requiredOutputBufferLength)
        {	outputBufferData = new byte[requiredOutputBufferLength];
        	outputBuffer.setData(outputBufferData);
        }
        
        if (!inputAudioFormat.equals(inputFormat))
        	throw new RuntimeException("Incorrect inpout format");
        
        if (inputAudioFormat.getEndian() == -1)
        	throw new RuntimeException("Unspecified endian-ness");	// TODO: check in setInputFormat
        final boolean bigEndian = inputAudioFormat.getEndian() == AudioFormat.BIG_ENDIAN;	
        MuLawEncoderUtil.muLawEncode(bigEndian, (byte []) inputBuffer.getData(), inputBuffer.getOffset(), inputBuffer.getLength(), outputBufferData);
        outputBuffer.setLength(requiredOutputBufferLength);
        outputBuffer.setOffset(0);
        outputBuffer.setFormat(outputFormat);
        
        final int result = BUFFER_PROCESSED_OK;
        
        
		if (TRACE)
		{	dump("input ", inputBuffer);
			dump("output", outputBuffer);
		
			System.out.println("Result=" + MediaCGUtils.plugInResultToStr(result));
		}
		return result;
	}

	public Format setInputFormat(Format arg0)
	{
		// TODO: force sample size, etc
		if (TRACE) System.out.println("setInputFormat: " + MediaCGUtils.formatToStr(arg0));
		return super.setInputFormat(arg0);
	}

	public Format setOutputFormat(Format arg0)
	{
		if (TRACE) System.out.println("setOutputFormat: " + MediaCGUtils.formatToStr(arg0));
		return super.setOutputFormat(arg0);
	}

	
}
